﻿using System.Collections.Generic;
using System.IO;

namespace Raven
{
    public class RavenMap
    {
        private SparseGraph<NavGraphNode<Trigger<RavenBot>>, NavGraphEdge> _navGraph;

        //the graph nodes will be partitioned enabling fast lookup
        private CellSpacePartition<NavGraphNode<Trigger<RavenBot>>> _spacePartition;
        private Fix _cellSpaceNeighborhoodRange;
        private int _sizeX;
        private int _sizeY;

        //the walls that comprise the current map's architecture. 
        private List<Wall2D> _walls = new List<Wall2D>();
        private TriggerSystem _triggerSystem = new TriggerSystem();
        private List<SpawnPoint> _spawnPoints = new List<SpawnPoint>();


        //this will hold a pre-calculated lookup table of the cost to travel from
        //one node to any other.
        private Fix[,] _pathCosts;

        private RavenGame _world;

        public RavenMap(RavenGame world)
        {
            _world = world;
        }

        public bool LoadMap(string filePath)
        {
            if (!File.Exists(filePath))
            {
                Logger.LogError("Bad Map Filename");
                return false;
            }

            Clear();
            BaseGameEntity.ResetNextValidID();

            _navGraph = new SparseGraph<NavGraphNode<Trigger<RavenBot>>, NavGraphEdge>(false);
            Stream input = new FileStream(filePath, FileMode.Open);
            BinaryReader binaryReader = new BinaryReader(input);
            _navGraph.Load(binaryReader);

            Logger.Log("NavGraph for " + Path.GetFileName(filePath) + " loaded okay");

            //determine the average distance between graph nodes so that we can
            //partition them efficiently
            _cellSpaceNeighborhoodRange = _navGraph.CalculateAverageGraphEdgeLength() + 1;
            Logger.Log("Average edge length is " + (_cellSpaceNeighborhoodRange - 1));
            Logger.Log("Neighborhood range set to " + _cellSpaceNeighborhoodRange);

            _sizeX = binaryReader.ReadInt32();
            _sizeY = binaryReader.ReadInt32();
            Logger.Log("Partitioning navgraph nodes...");
            PartitionNavGraph();

            while (binaryReader.BaseStream.Position < binaryReader.BaseStream.Length)
            {
                // todo LoadMap
                EntityType type = (EntityType) binaryReader.ReadInt32();
                switch (type)
                {
                    case EntityType.Wall:
                        AddWall(binaryReader);
                        break;
                    case EntityType.SpawnPoint:
                        AddSpawnPoint(binaryReader);
                        break;
                    case EntityType.Health:
                        AddHealthGiver(binaryReader);
                        break;
                    case EntityType.RocketLauncherWeaponGiver:
                        AddWeaponGiver(type, WeaponType.type_rocket_launcher, binaryReader);
                        break;

                    case EntityType.ShotgunWeaponGiver:
                        AddWeaponGiver(type, WeaponType.type_shotgun, binaryReader);
                        break;

                    case EntityType.RailGunWeaponGiver:
                        AddWeaponGiver(type, WeaponType.type_rail_gun, binaryReader);
                        break;
                    default:
                        Logger.LogError(type + " not load");
                        break;
                }
            }

            binaryReader.Close();
            input.Close();

            Logger.Log(Path.GetFileName(filePath) + " loaded okay");

            //calculate the cost lookup table
            _pathCosts = CreateAllPairsCostsTable();

            return true;
        }

        void PartitionNavGraph()
        {
            _spacePartition = new CellSpacePartition<NavGraphNode<Trigger<RavenBot>>>(_sizeX,
                _sizeY, Constants.NumCellsX, Constants.NumCellsY, _navGraph.NumNodes());

            foreach (NavGraphNode<Trigger<RavenBot>> node in _navGraph)
            {
                _spacePartition.AddEntity(node);
            }
        }

        Fix[,] CreateAllPairsCostsTable()
        {
            //create a two dimensional vector
            int numNodes = _navGraph.NumNodes();
            Fix[,] PathCosts = new Fix[numNodes, numNodes];

            for (int source = 0; source < _navGraph.NumNodes(); ++source)
            {
                //do the search
                GraphSearchDijkstra<NavGraphNode<Trigger<RavenBot>>, NavGraphEdge> search =
                    new GraphSearchDijkstra<NavGraphNode<Trigger<RavenBot>>, NavGraphEdge>(_navGraph, source);

                //iterate through every node in the graph and grab the cost to travel to
                //that node
                for (int target = 0; target < _navGraph.NumNodes(); ++target)
                {
                    if (source != target)
                    {
                        PathCosts[source, target] = search.GetCostToNode(target);
                    }
                } //next target node
            } //next source node

            return PathCosts;
        }


        public void Clear()
        {
            _triggerSystem.Clear();
            _walls = new List<Wall2D>();
            _spawnPoints = new List<SpawnPoint>();
        }

        public void AddWall(BinaryReader reader)
        {
            _walls.Add(new Wall2D(reader));
        }

        public void AddSpawnPoint(BinaryReader reader)
        {
            _spawnPoints.Add(new SpawnPoint(reader));
        }

        public void AddHealthGiver(BinaryReader reader)
        {
            TriggerHealthGiver hg = new TriggerHealthGiver(reader);

            _triggerSystem.Register(hg);

            //let the corresponding navgraph node point to this object
            NavGraphNode<Trigger<RavenBot>> node = _navGraph.GetNode(hg.GraphNodeIndex());

            node.SetExtraInfo(hg);

            //register the entity 
            _world.EntityManager.RegisterEntity(hg);
        }

        public void AddWeaponGiver(EntityType entityType, WeaponType weaponType, BinaryReader reader)
        {
            TriggerWeaponGiver wg = new TriggerWeaponGiver(entityType, weaponType, reader);

            //add it to the appropriate vectors
            _triggerSystem.Register(wg);

            //let the corresponding navgraph node point to this object
            NavGraphNode<Trigger<RavenBot>> node = _navGraph.GetNode(wg.GraphNodeIndex());

            node.SetExtraInfo(wg);

            //register the entity 
            _world.EntityManager.RegisterEntity(wg);
        }


        public int GetSizeX()
        {
            return _sizeX;
        }

        public int GetSizeY()
        {
            return _sizeY;
        }

        public List<Wall2D> GetWalls()
        {
            return _walls;
        }

        public List<SpawnPoint> GetSpawnPoints()
        {
            return _spawnPoints;
        }

        public Vector2 GetRandomSpawnPoint()
        {
            return _spawnPoints[_world.GetRandom().RandInt(0, _spawnPoints.Count)].Pos();
        }

        public SparseGraph<NavGraphNode<Trigger<RavenBot>>, NavGraphEdge> GetNavGraph()
        {
            return _navGraph;
        }

        //---------------------------- AddSoundTrigger --------------------------------
        //
        //  given the bot that has made a sound, this method adds a SoundMade trigger
        //-----------------------------------------------------------------------------
        public void AddSoundTrigger(RavenBot pSoundSource, Fix range)
        {
            //_triggerSystem.Register(new TriggerSoundNotify(pSoundSource, _world, range));
        }

        //------------------------- GetRandomNodeLocation -----------------------------
        //
        //  returns the position of a graph node selected at random
        //-----------------------------------------------------------------------------
        public Vector2 GetRandomNodeLocation()
        {
            List<Vector2> locations = new List<Vector2>();
            foreach (NavGraphNode<Trigger<RavenBot>> node in _navGraph)
            {
                if (node.Index() != Constants.INVALID_NODE_INDEX)
                {
                    locations.Add(node.Pos());
                }
            }

            int RandIndex = _world.GetRandom().RandInt(0, locations.Count - 1);

            return locations[RandIndex];
        }

        public LinkedList<Trigger<RavenBot>> GetTriggers()
        {
            return _triggerSystem.GetTriggers();
        }

        public void UpdateTriggerSystem(IEnumerable<RavenBot> entities)
        {
            _triggerSystem.Update(entities);
        }

        public Fix GetCellSpaceNeighborhoodRange()
        {
            return _cellSpaceNeighborhoodRange;
        }

        public CellSpacePartition<NavGraphNode<Trigger<RavenBot>>> GetCellSpace()
        {
            return _spacePartition;
        }

        //------------- CalculateCostToTravelBetweenNodes -----------------------------
        //
        //  Uses the pre-calculated lookup table to determine the cost of traveling
        //  from nd1 to nd2
        //-----------------------------------------------------------------------------
        public Fix CalculateCostToTravelBetweenNodes(int nd1, int nd2)
        {
            Utility.Assert(nd1 >= 0 && nd1 < _navGraph.NumNodes() &&
                           nd2 >= 0 && nd2 < _navGraph.NumNodes(),
                "<Raven_Map::CostBetweenNodes>: invalid index");

            return _pathCosts[nd1, nd2];
        }
    }
}